﻿/*
 * @author: wizardc
 */

using System;
using System.Collections.Generic;
using System.Reflection;
using UnityEngine;
using UnityEngine.UI;

namespace Dou.UI
{
    /// <summary>
    /// 位于 RectMask2D 下的 UI，如果添加了 Canvas 并且启用了 OverrideSorting 那么就会脱离上层的遮罩，该组件用于将这类 UI 重新纳入上层的遮罩中
    /// 1. 添加本组件到已经添加了 Canvas 并且启用了 OverrideSorting 的 GameObject 上，就可以续上之前的遮罩；
    /// 2. 不支持 Mask 组件；
    /// 3. 当前 GO 提供的 Rect 区域不会进行遮罩裁剪；
    /// </summary>
    public class CanvasRectMask2D : RectMask2D
    {
        private const BindingFlags Flags = BindingFlags.Instance | BindingFlags.NonPublic;
        private static Type _type;
        
        private RectMask2D _originParentMask;

        [NonSerialized]
        private HashSet<MaskableGraphic> m_MaskableTargets = new HashSet<MaskableGraphic>();
        [NonSerialized]
        private HashSet<IClippable> m_ClipTargets = new HashSet<IClippable>();
        [NonSerialized]
        private List<RectMask2D> m_Clippers = new List<RectMask2D>();
        
        private Canvas m_Canvas;
        private Rect m_LastClipRectCanvasSpace;
        private Vector3[] m_Corners = new Vector3[4];
        private bool m_ForceClip;
        
        private Canvas Canvas
        {
            get
            {
                if (m_Canvas == null)
                {
                    m_Canvas = (Canvas)GetField("Canvas");
                }
                return m_Canvas;
            }
        }

        private Rect rootCanvasRect
        {
            get
            {
                rectTransform.GetWorldCorners(m_Corners);
                if (!ReferenceEquals(Canvas, null))
                {
                    Canvas rootCanvas = Canvas.rootCanvas;
                    for (int i = 0; i < 4; ++i)
                        m_Corners[i] = rootCanvas.transform.InverseTransformPoint(m_Corners[i]);
                }
                return new Rect(m_Corners[0].x, m_Corners[0].y, m_Corners[2].x - m_Corners[0].x, m_Corners[2].y - m_Corners[0].y);
            }
        }
        
        private void SetField(string fieldName, object value)
        {
            if (_type == null)
            {
                _type = GetType().BaseType;
            }
            FieldInfo fieldInfo = _type.GetField(fieldName, Flags);
            fieldInfo.SetValue(this, value);
        }

        private object GetField(string fieldName)
        {
            if (_type == null)
            {
                _type = GetType().BaseType;
            }
            FieldInfo fieldInfo = _type.GetField(fieldName, Flags);
            PropertyInfo propertyInfo = _type.GetProperty(fieldName, Flags);
            object value = fieldInfo == null ? propertyInfo.GetValue(this) : fieldInfo.GetValue(this);
            return value;
        }

        protected override void Awake()
        {
            base.Awake();
            Init();
        }

        private void Init()
        {
            if (_type == null)
            {
                _type = GetType().BaseType;
                m_Clippers = (List<RectMask2D>)GetField("m_Clippers");
                m_ClipTargets = (HashSet<IClippable>)GetField("m_ClipTargets");
                m_MaskableTargets = (HashSet<MaskableGraphic>)GetField("m_MaskableTargets");
            }
        }

        protected override void OnEnable()
        {
            _originParentMask = GetParent();
            base.OnEnable();
        }
        
        private RectMask2D GetParent()
        {
            Canvas canvas = transform.GetComponent<Canvas>();
            if (canvas == null || (!canvas.overrideSorting))
            {
                return null;
            }
            Transform current = transform.parent;
            while (current != null)
            {
                RectMask2D parentRectMask2D = current.GetComponent<RectMask2D>();
                if (parentRectMask2D != null)
                {
                    return parentRectMask2D;
                }
                current = current.parent;
            }
            return null;
        }
        
        protected override void OnTransformParentChanged()
        {
            base.OnTransformParentChanged();
            _originParentMask = GetParent();
        }

        protected override void OnCanvasHierarchyChanged()
        {
            m_Canvas = null;
            base.OnCanvasHierarchyChanged();
        }

        public override void PerformClipping()
        { 
            if (_originParentMask == null)
            {
                base.PerformClipping();
            }
            else
            {
                if (ReferenceEquals(Canvas, null))
                {
                    return;
                }

                bool m_ShouldRecalculateClipRects = (bool) GetField("m_ShouldRecalculateClipRects");
                
                if (m_ShouldRecalculateClipRects)
                {
                    List<RectMask2D> this_Clippers = new List<RectMask2D>();
                    MaskUtilities.GetRectMasksForClip(_originParentMask, m_Clippers);
                    MaskUtilities.GetRectMasksForClip(this, this_Clippers);
                    foreach (RectMask2D clipper in this_Clippers)
                    {
                        if (clipper == this) // CanvasRectMask2D 只是用来标明当前的Canvas需要使用上父级的遮罩的组件，不需要加上自身的裁剪区域，避免影响原有的效果
                        {
                            continue;
                        }
                        if (!m_Clippers.Contains(clipper))
                        {
                            m_Clippers.Add(clipper);
                        }
                    }
                    SetField("m_ShouldRecalculateClipRects",false);
                }

                bool validRect = true;
                Rect clipRect = Clipping.FindCullAndClipWorldRect(m_Clippers, out validRect);
                
                RenderMode renderMode = Canvas.rootCanvas.renderMode;
                bool maskIsCulled =
                    (renderMode == RenderMode.ScreenSpaceCamera || renderMode == RenderMode.ScreenSpaceOverlay) &&
                    !clipRect.Overlaps(rootCanvasRect, true);
                
                bool clipRectChanged = clipRect != m_LastClipRectCanvasSpace;

                if (maskIsCulled)
                {
                    clipRect = Rect.zero;
                    validRect = false;
                }

                bool forceClip = m_ForceClip;

                if (clipRectChanged)
                {
                    foreach (IClippable clipTarget in m_ClipTargets)
                    {
                        clipTarget.SetClipRect(clipRect, validRect);
                    }

                    foreach (MaskableGraphic maskableTarget in m_MaskableTargets)
                    {
                        maskableTarget.SetClipRect(clipRect, validRect);
                        maskableTarget.Cull(clipRect, validRect);
                    }
                }
                else if (forceClip)
                {
                    foreach (IClippable clipTarget in m_ClipTargets)
                    {
                        clipTarget.SetClipRect(clipRect, validRect);
                    }

                    foreach (MaskableGraphic maskableTarget in m_MaskableTargets)
                    {
                        maskableTarget.SetClipRect(clipRect, validRect);

                        if (maskableTarget.canvasRenderer.hasMoved)
                            maskableTarget.Cull(clipRect, validRect);
                    }
                }
                else
                {
                    foreach (MaskableGraphic maskableTarget in m_MaskableTargets)
                    {
                        //Case 1170399 - hasMoved is not a valid check when animating on pivot of the object
                        maskableTarget.Cull(clipRect, validRect);
                    }
                }

                m_LastClipRectCanvasSpace = clipRect;
                m_ForceClip = false;

                UpdateClipSoftness();
            }
        }
        
        public override void UpdateClipSoftness()
        {
            if (ReferenceEquals(Canvas, null))
            {
                return;
            }

            foreach (IClippable clipTarget in m_ClipTargets)
            {
                clipTarget.SetClipSoftness(_originParentMask.softness);
            }

            foreach (MaskableGraphic maskableTarget in m_MaskableTargets)
            {
                maskableTarget.SetClipSoftness(_originParentMask.softness);
            }
        }
        
        public new void AddClippable(IClippable clippable)
        {
            base.AddClippable(clippable);
            m_ForceClip = true;
        }

        public new void RemoveClippable(IClippable clippable)
        {
            base.RemoveClippable(clippable);
            m_ForceClip = true;
        }
    }
}
